module server.TransferChannel;

import server.socket;
import std.c.windows.winsock;
import std.c.windows.windows;
import std.file;
import std.stdio;
import std.random;


enum ChannelStatus
{
	CREATED,
	IDLE,
	ACTIVE,
	FAILED,
	CLOSED
}

enum TransferType
{
	Block,
	Async
}


typedef  bool delegate(Transfer trans, Object context) TransferDelegate;

import server.ControlChannel; //avoid the forward reference

//TODO: what if a transfer gets put on a transfer channel in which the connection times out
//before the connection is finished


class TransferChannel : Socket
{
	public ControlChannel owner;
	public ChannelStatus status;
	
	public Transfer[] transfers;
	
	public ~this()
	{
		
	}
	
	public this(ControlChannel owner)
	{
		status = ChannelStatus.CREATED;
		owner = owner;
	}
	
	override protected void on_connect(int err)
	{
		if (err != 0)
		{
			debug writefln("transfer unable to connect (%d)", err);
			owner.raise(err, "tranfer unable to connect");
			status = ChannelStatus.FAILED;
		}
		status = ChannelStatus.IDLE;
		debug writefln("transfer channel connected");
	}
	
	override protected void on_close(int err)
	{
		if (status != ChannelStatus.IDLE)
		{
			status = ChannelStatus.FAILED;
			owner.raise("transfer closed prematurely");
			debug writefln("connection closed before all data was transfered");
		}
		else
		{
			status = ChannelStatus.CLOSED;
			debug writefln("transfer channel connection closed");
		}
	}
	
	override protected void on_write()
	{
		if (transfers.length > 0)
		{
			transfers[0].on_write();
			if (transfers[0].active == false)
			{
				transfers = transfers[1..$];
				if (transfers.length > 0)
					transfers[0].on_write();
			}
		}
	}
	
	public Transfer new_transfer(TransferType type, TransferDelegate dg = null, Object dg_context = null)
	{
		Transfer tr = new Transfer(this, std.random.rand(), type, dg, dg_context);
		
		transfers ~= tr;
		if (transfers.length == 1)
		{
			//TODO: FIX:
			//IF THE CHANNEL IS ALREADY OPEN AND THE TRANSFERS LENGTH IS ZERO
			//THEN WE MUST INITIATE THE FIRST transfer.on_write()
			//write the transfer id
		}
		return tr;
	}
}

class Transfer
{
	private TransferChannel chan;
	private static uint g_transfer_id;
	public uint trans_id;
	public TransferType type;
	
	private ubyte[] buf;
	private int buf_len;
	private TransferDelegate dg;
	private Object dg_ctx;
	
	bool active;
	bool finished;
	
	//public properties
	public uint transfer_id()
	{
		return trans_id;
	}
	
	public TransferChannel channel()
	{
		return chan;
	}
	
	//constructors
	public static this()
	{
		g_transfer_id = GetTickCount();
	}	
	
	public this(TransferChannel chan, int trans_id = -1, TransferType type = TransferType.Block, TransferDelegate dg = null, Object dg_ctx = null)
	{
		this.chan = chan;
		if (trans_id > -1)
			this.trans_id = cast(uint)trans_id; //safe
		else
			this.trans_id = g_transfer_id;
		
		active = false;
		this.type = type;
		this.dg = dg;
		this.dg_ctx = dg_ctx;
		
		/*
		 * if the transfer is asynchronous then there is no reason to allocate a sending buffer.
		 * the idea with asynchronous transfers is to transfer data in chunks one at a time specifically
		 * to avoid copying memory/allocating all of the data to send at once.
		 */
		if (type == TransferType.Async)
		{
			buf_len = 0;
			buf.length = 1024;
		}
	}
	
	private void expand()
	{
		buf.length = buf.length * 2;
	}
	
	public void finish()
	{
		finished = true;
	}
	
	public void on_write()
	{
		int r;
		if (active == false)
		{
			//this is the first time this transfer object's on_write has been called
			//first send the transfer id
			active = true;
			r = chan.send(get_bytes(trans_id));
			if (r != trans_id.sizeof)
			{
				//TODO: handle gracefully
				throw new SocketException("unable to send transfer id");
			}
		}
		
		write_all();
		if ((type == TransferType.Block) && (buf_len == 0) && (finished == true))
		{
			active = false;
		} else
		if ((type == TransferType.Async) && (buf_len == 0))
		{
			/*
			 * the following loops are necessary to make sure
			 * that an FD_SEND event gets posted to the socket.
			 * 
			 * FD_WRITE is only posted when a send() call
			 * fails with WSAEWOULDBLOCK
			 */
			bool all_sent;
			do 
			{
				//done
				if (dg(this, dg_ctx) == false)
				{
					active = false;
					finished = true;
					break;
				}
				
				all_sent = write_all();
			} while (all_sent);
		}
	}
	
	private bool write_all()
	{
		int r, i;
		while (buf_len > 0)
		{
			r = chan.send(buf[0.. buf_len > 2048 ? 2048 : buf_len]);
			if (r == 0)
			{
				//TODO: handle gracefully?
				//connection closed
				return false;
			}
			if (r == -1)
				return false;
			buf_len -= r;
			buf = buf[r..$];
		}
		return true;
	}
	
	//TODO: finish
	public void write(ubyte[] data)
	{
		int roundup(int x, int roundto)
		{
			return ((x / roundto)+1) * roundto;
		}
		
		int len = data.length;
		int new_len = buf_len + len;
		
		/*
		 * if its not async then we just append the data and wait for it to be sent
		 * if it is async and there is already data waiting to be written, then just append
		 */
		
		//TODO: fix/finish
		if (buf_len > 0)
		{
			if (new_len > buf.length) //we need to length buf
			{
				if ((type == TransferType.Async) && (buf.length == buf_len))	
				{
					//thats test is how we check if this is the first time buffer is being expanded
					//if so, we don't own the buffer so we must copy it.
					buf = buf.dup;
					buf_len = buf.length;
					buf.length = roundup(buf.length, 512);
				}
				else
				{
					buf.length = buf.length * 2;
				}
			}
			
			buf[buf_len..buf_len+len] = data;
			buf_len += len;
		}
		else
		{
			//no data waiting to be sent so we just reference it.
			buf_len = len;
			buf = data;
		}
	}
	
	public ~this()
	{
		
	}
	
	
}